package com.dd.cloud.user.aspect;

import com.dd.cloud.common.annotate.OperLog;
import com.dd.cloud.common.enums.ZkExceptionEnum;
import com.dd.cloud.common.exception.ZekeException;
import com.dd.cloud.common.exception.ZekeOpenApiException;
import com.dd.cloud.common.interceptor.UserInterceptor;
import com.dd.cloud.common.single.BlockKeywordsSingle;
import com.dd.cloud.common.utils.CookieUtils;
import com.dd.cloud.common.utils.IPUtil;
import com.dd.cloud.common.utils.JsonUtils;
import com.dd.cloud.common.vo.LoginInfo;
import com.dd.cloud.user.entity.log.LogException;
import com.dd.cloud.user.entity.log.LogModul;
import com.dd.cloud.user.entity.log.LogOper;
import com.dd.cloud.user.service.log.ILogExceptionService;
import com.dd.cloud.user.service.log.ILogModulService;
import com.dd.cloud.user.service.log.ILogOperService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author Lx
 * @version 1.0
 */
@Slf4j
@Aspect
@Configuration
public class LogAop {
    @Autowired
    private ILogOperService logOperService;
    @Autowired
    private ILogExceptionService logExcService;
    @Autowired
    private ILogModulService logModulService;
    @Value("${version.name}")
    private String name;
    @Value("${version.value}")
    private String value;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final Integer TIME_OUT = 60 * 24; //redis 存活时长  分钟

    /**
     * 定义切入点，即拦截规则 切入点为com.zekekeji.controller中的所有接口函数,
     * 不拦截查询医生是否交服务费的接口
     * 通过@Pointcut注解声明频繁使用的切点表达式
     */
    @Pointcut("execution(public * com.dd.cloud.user.controller..*.*(..)) ")
    public void OperLogAspect() {

    }


    /**
     * @description 在连接点执行之前执行的通知 用来识别是否有屏蔽词
     */
    @Before("OperLogAspect()")
    public void doBeforeGame(JoinPoint joinPoint) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);
        if (HttpMethod.GET.equals(request.getMethod())) {
            return;
        }
        String contentType = request.getContentType();
        if (StringUtils.isBlank(contentType)) {
            return;
        }
        if (contentType.indexOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE) >= 0) {
            // 获取所有参数的名称
            Enumeration<String> parameterNames = request.getParameterNames();
            while (parameterNames.hasMoreElements()) {
                String paramName = parameterNames.nextElement();
                String paramValue = request.getParameter(paramName);
                if (StringUtils.isNotBlank(paramValue) && !isInteger(paramValue)) {
                    // 处理参数
                    boolean hasBlockKeyword = BlockKeywordsSingle.hasBlockKeyword(paramValue);
                    if (hasBlockKeyword) {
                        //return resExp(response, ZkExceptionEnum.HAS_BLOCK_KEYWORD, null);
                        throw new ZekeException(ZkExceptionEnum.HAS_BLOCK_KEYWORD);
                    }
                }
            }
        }
        if (request.getContentType().indexOf(MediaType.APPLICATION_JSON_VALUE) >= 0) {
            //json类型参数
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
            String[] paramNames = u.getParameterNames(method);
            //方法 1 请求的方法参数值 JSON 格式 null不显示
            if (joinPoint.getArgs().length > 0) {
                Object[] args = joinPoint.getArgs();
                for (int i = 0; i < args.length; i++) {
                    //请求参数类型判断过滤，防止JSON转换报错
                    if (args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse || args[i] instanceof MultipartFile) {
                        continue;
                    }
                    String str = String.valueOf(args[i]);
                    if (StringUtils.isNotBlank(str) && !isInteger(str)) {
                        // 处理参数
                        boolean hasBlockKeyword = BlockKeywordsSingle.hasBlockKeyword(str);
                        if (hasBlockKeyword) {
                            //return resExp(response, ZkExceptionEnum.HAS_BLOCK_KEYWORD, null);
                            throw new ZekeException(ZkExceptionEnum.HAS_BLOCK_KEYWORD);
                        }
                    }
                }
            }
        }
    }

    /**
     * @description 在连接点执行之后执行的通知（返回通知和异常通知的异常）
     */
    //@After("OperLogAspect()")
    public void doAfterGame() {
        //log.info("在连接点执行之后执行的通知（返回通知和异常通知的异常）");

    }

    /**
     * @description 在连接点执行之后执行的通知（返回通知）
     * 记录操作日志
     */
    @AfterReturning(value = "OperLogAspect()", returning = "keys")
    public void doAfterReturningGame(JoinPoint joinPoint, Object keys) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);
        LogOper operlog = new LogOper();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            // 获取操作
            OperLog opLog = method.getAnnotation(OperLog.class);
            if (opLog != null) {
                String operModul = opLog.operModule();
                Integer operType = opLog.operType();
                String operDesc = opLog.operDesc();
                // 操作模块
                operlog.setModular(operModul);
                // 操作类型
                operlog.setOperType(operType);
                // 操作描述
                operlog.setOperDesc(operDesc);
            } else {
                LogModul logModul = logModulService.getLogModulByUrl(request.getRequestURI());
                // 操作模块
                operlog.setModular(logModul.getModular());
                // 操作类型
                operlog.setOperType(logModul.getType());
                // 操作描述
                operlog.setOperDesc(logModul.getOperDesc());
                // 功能备注
                operlog.setRemarks(logModul.getRemarks());
            }
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName;
            // 请求方法
            operlog.setOperMethod(methodName);
            LoginInfo loginInfo = UserInterceptor.getLoginUserInfo().get();
            // 请求的参数
            Map<String, String> rtnMap = converMap(request.getParameterMap());
            // 将参数所在的数组转换成json
            String params = JsonUtils.objectToStr(rtnMap);
            // 请求参数
            operlog.setOperReqParams(params);
            // 返回结果
            operlog.setOperResParams(JsonUtils.objectToStr(keys));
            if (loginInfo != null && loginInfo.getId() != null) {
                operlog.setUserId(loginInfo.getId());
                operlog.setUserType(loginInfo.getType());
                operlog.setName(loginInfo.getName());
            }
            // 请求IP
            operlog.setOperIp(IPUtil.getIpAddress(request));
            // 请求URI
            operlog.setOperUrl(request.getRequestURI());
            // 创建时间
            operlog.setCreateDate(LocalDateTime.now());
            String client = request.getHeader("client");
            // 操作客户端
            operlog.setOperVer(client);
            logOperService.addOperLogMQ(operlog);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @description 在连接点执行之后执行的通知（异常通知）
     * 记录异常日志
     */
    @AfterThrowing(pointcut = "OperLogAspect()", throwing = "ex")
    public void doAfterThrowingGame(JoinPoint joinPoint, Exception ex) throws Exception {
        log.info("在连接点执行之后执行的通知（异常通知）");
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);
        LogException excepLog = new LogException();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            // 获取切入点所在的方法
            Method method = signature.getMethod();
            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            // 获取请求的方法名
            String methodName = method.getName();
            methodName = className + "." + methodName;
            // 请求的参数
            Map<String, String> rtnMap = converMap(request.getParameterMap());
            LoginInfo loginInfo = UserInterceptor.getLoginUserInfo().get();
            // 将参数所在的数组转换成json
            String params = JsonUtils.objectToStr(rtnMap);
            excepLog.setOperReqParams(params); // 请求参数
            excepLog.setOperMethod(methodName); // 请求方法名
            excepLog.setExcName(ex.getClass().getName()); // 异常名称
            excepLog.setExcMsg(stackTraceToString(ex.getClass().getName(), ex.getMessage(), ex.getStackTrace())); // 异常信息
            if (ex instanceof ZekeException) {
                ZekeException zk = (ZekeException) ex;
                excepLog.setExcMsg(((ZekeException) ex).getMsg()); // 异常信息
                if (StringUtils.isBlank(zk.getMsg())) {
                    excepLog.setExcMsg(zk.getMsg()); // 异常信息
                }
            }
            if (ex instanceof ZekeOpenApiException) {
                ZekeOpenApiException zk = (ZekeOpenApiException) ex;
                excepLog.setExcMsg(((ZekeOpenApiException) ex).getCodeMsg()); // 异常信息
            }
            if (loginInfo != null && loginInfo.getId() != null) {
                excepLog.setUserId(loginInfo.getId()); // 请求用户ID
                excepLog.setUserType(loginInfo.getType()); // 请求用户名称
            }
            LogModul logModul = logModulService.getLogModulByUrl(request.getRequestURI());
            // 操作模块
            excepLog.setModular(logModul.getModular());
            // 操作类型
            excepLog.setOperType(logModul.getType());
            // 操作描述
            excepLog.setOperDesc(logModul.getOperDesc());
            // 功能备注
            excepLog.setRemarks(logModul.getRemarks());
            excepLog.setOperUrl(request.getRequestURI()); // 操作URI
            excepLog.setOperIp(IPUtil.getIpAddress(request)); // 操作员IP
            String version = CookieUtils.getCookieValue(request, name);
            excepLog.setOperVer(version); // 操作版本
            if (StringUtils.isBlank(version)) {
                excepLog.setOperVer(value); // 操作版本
            }
            excepLog.setCreateDate(LocalDateTime.now()); // 发生异常时间
            logExcService.addLogExcMQ(excepLog);
        } catch (Exception e2) {
            e2.printStackTrace();
        }
        throw ex;
    }

    /**
     * @description 使用环绕通知
     * 使用redis缓存, get请求时查询缓存, post请求时移除对应的缓存
     */
    //@Around("queryCachePointcut()")
    public Object doAroundGame(ProceedingJoinPoint joinPoint) throws Throwable {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        String requMethod = request.getMethod();

        long beginTime = System.currentTimeMillis();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        //类路径名
        String classPathName = joinPoint.getTarget().getClass().getName();
        //类名
        String className = classPathName.substring(classPathName.lastIndexOf(".") + 1);
        //获取方法名
        String methodName = signature.getMethod().getName();
        //参数名
        String[] strings = signature.getParameterNames();
        //参数值
        Object[] method_args = joinPoint.getArgs();
        String params = "";
        for (int i = 0; i < strings.length; i++) {
            params += strings[i] + "=" + method_args[i] + ",";
        }
        //拼接参数和参数值
        //params = params.substring(0, params.length() - 1);
        LoginInfo loginInfo = UserInterceptor.getLoginUserInfo().get();
        String key = className + "_" + methodName + "_" + params;
        if (loginInfo != null) {
            key += "_" + loginInfo.getId() + "_" + loginInfo.getType();
        }
        //log.info("请求方法"+requMethod);
        //log.info("请求key"+key);
        // 如果是查询接口 则获取缓存
        if ("GET".equals(requMethod)) {
            Object data = getObject(beginTime, joinPoint, key);
            if (data != null) {
                //return data;
                //Map map = (Map) data;
                //ResponseEntity<Object> res = JsonUtils.objectToVo(data,ResponseEntity.class);
                //ResponseEntity<Object> res = (ResponseEntity<Object>) data;
                try {
                    data = ResponseEntity.ok(data);
                    return data;
                } catch (ClassCastException e) {
                    return data;
                }
                //return JsonUtils.parse(JsonUtils.serialize(data), ResPonseEntity.class);
                //return res;
            }
            return joinPoint.proceed();
            // 如果是更改或新增数据 则填充缓存
        } else if ("POST".equals(requMethod)) {
            Set<String> keys = redisTemplate.keys(className + "*");
            redisTemplate.delete(keys);
            log.warn("执行方法 : [ " + methodName + " ] :  清除 key 包含 [ " + className + " ] 的缓存数据");
            //log.warn("AOP 清除缓存 >>>> end 耗时：" + (System.currentTimeMillis() - beginTime) + "ms.");
        }
        // 调用原始方法
        return joinPoint.proceed();
    }

    /**
     * @param beginTime : 切面开始时间
     * @param joinPoint : 切面对象
     * @param key       : 获取redis数据的key值
     * @return java.lang.Object
     * @Title: getObject
     * @Description: 使用key获取数据  不存在则查询添加
     **/
    private Object getObject(long beginTime, ProceedingJoinPoint joinPoint, String key) throws Throwable {
        ValueOperations<String, Object> operations = redisTemplate.opsForValue();
        boolean hasKey = redisTemplate.hasKey(key);
        //缓存中获取的数据
        Object cacheObj = null;
        //返回的数据
        Object resObj = null;
        ResponseEntity entity = null;
        if (hasKey) {
            // 缓存中获取到数据，直接返回。
            cacheObj = operations.get(key);
            //object = JSON.parse((byte[]) operations.get(key));
            log.warn("从缓存中获取到 key 为 [" + key + " ] : 的数据 &gt;&gt;&gt;&gt; " + cacheObj.toString());
            //log.warn("AOP 缓存切面处理 &gt;&gt;&gt;&gt; end 耗时：" + (System.currentTimeMillis() - beginTime));
            return cacheObj;
        }
        if (cacheObj == null) {
            // 缓存中没有数据，调用原始方法查询数据库
            cacheObj = joinPoint.proceed();
            String json = "";
            try {
                entity = (ResponseEntity) cacheObj;
                json = JsonUtils.objectToStr(entity.getBody());
                resObj = entity.getBody();
            } catch (Exception e) {
                resObj = cacheObj;
                json = JsonUtils.objectToStr(entity.getBody());
            }

            redisTemplate.opsForValue().set(key, json, TIME_OUT, TimeUnit.MINUTES);
            //operations.set(key, entity, TIME_OUT, TimeUnit.MINUTES); // 设置超时时间30分钟
            log.warn("向 Redis 添加 key 为 [" + key + " ] , 存活时长为 " + TIME_OUT + " min 的数据 &gt;&gt;&gt;&gt; " + cacheObj.toString());
            //log.warn("AOP 缓存切面处理 &gt;&gt;&gt;&gt; end 耗时：" + (System.currentTimeMillis() - beginTime));
        }
        return resObj;
    }

    @Autowired(required = false)
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        RedisSerializer stringSerializer = new StringRedisSerializer();//序列化为String
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);//序列化为Json
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        this.redisTemplate = redisTemplate;
    }

    /**
     * 转换request 请求参数
     *
     * @param paramMap request获取的参数数组
     */
    public Map<String, String> converMap(Map<String, String[]> paramMap) {
        Map<String, String> rtnMap = new HashMap<String, String>();
        for (String key : paramMap.keySet()) {
            rtnMap.put(key, paramMap.get(key)[0]);
        }
        return rtnMap;
    }

    /**
     * 转换异常信息为字符串
     *
     * @param exceptionName    异常名称
     * @param exceptionMessage 异常信息
     * @param elements         堆栈信息
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "\n");
        }
        String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff;
        return message;
    }

    public boolean isInteger(String str) {
        if (str != null && !"".equals(str.trim())) {
            return str.matches("^[0-9]*$");
        } else {
            return false;
        }
    }

}
